package com.mumu.muPictureBackend.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mumu.muPictureBackend.constant.UserConstant;
import com.mumu.muPictureBackend.exception.BusinessException;
import com.mumu.muPictureBackend.exception.ErrorCode;
import com.mumu.muPictureBackend.exception.ThrowUtils;
import com.mumu.muPictureBackend.mapper.UserMapper;
import com.mumu.muPictureBackend.model.vo.UserVO;
import com.mumu.muPictureBackend.model.dto.user.UserLoginRequest;
import com.mumu.muPictureBackend.model.dto.user.UserRegisterRequest;
import com.mumu.muPictureBackend.model.entity.User;
import com.mumu.muPictureBackend.model.enums.UserRoleEnum;
import com.mumu.muPictureBackend.service.AdminService;
import com.mumu.muPictureBackend.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.List;
import java.util.Objects;

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource//这个和auto的区别就是 这个是按名字进行查询 auto是根据类型进行查询 auto有可能出错的
    AdminService adminService;

    /**
     * 用户注册
     *
     * @param userRegisterRequest
     */
    @Override
    public Long userRegister(UserRegisterRequest userRegisterRequest) {
        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String checkPassword = userRegisterRequest.getCheckPassword();
        //1. 校验参数 如果这里面有的未空 那么就不合格呀 层层校验 直接利用自定义的ThrowUtils工具类 将条件和异常信息传进去 如果条件为真 那么就抛出异常  就不用写那么多if了 更加优雅
        ThrowUtils.throwIf(StrUtil.hasBlank(userAccount, userPassword, checkPassword), ErrorCode.PARAMS_ERROR, "账号、密码或确认密码不能为空");
        ThrowUtils.throwIf(userAccount.length() < 4, ErrorCode.PARAMS_ERROR, "账号过短 账号长度不能小于4");
        ThrowUtils.throwIf(userPassword.length() < 8, ErrorCode.PARAMS_ERROR, "密码长度过短 密码长度不能小于8");
        ThrowUtils.throwIf(!userPassword.equals(checkPassword), ErrorCode.PARAMS_ERROR, "两个密码不一致");
        // 2. account不能重复 思路:在Mysql中查找account是否存在 如果存在 那么就抛出错误 表示账号已存在 这里查询用count 每次返回Long而不是一个对象 更快 用count()
//        Long userCount = query().eq("userAccount", userAccount).count();
        //最快! 看到第一个account就直接返回 返回boolean
        boolean exist = query().eq("userAccount", userAccount).exists();
//        User user = query().eq("userAccount", userAccount).one();
        //如果当前账号已经存在 那么就抛出异常 提醒换个账号
        ThrowUtils.throwIf(exist, ErrorCode.PARAMS_ERROR, "该账号已存在 请换个账号!");

        //3. 密码加密
        String encryptPassword = getEncryptPassword(userPassword);
        //4. 将账户信息存储到数据库中
        User user = new User();
        //注意!:这里没必要用属性copy类 因为这里需要赋值加密后的密码!
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setUserName("无名氏");
        user.setUserRole(UserRoleEnum.USER.getText());
        //保存用户到数据库中
        log.info("用户注册成功 往数据库插入信息::{}", user);
        ThrowUtils.throwIf(!save(user), ErrorCode.SYSTEM_ERROR, "数据库错误 注册失败");
        //保存成功就返回id 这里的用户id也是在User类中自动用雪花算法生成的
        return user.getId();
    }

    /**
     * 密码加密
     */
    @Override
    public String getEncryptPassword(String password) {
        //给密码再加一道
        final String SALT = "MuMu";
        return DigestUtils.md5DigestAsHex((SALT + password).getBytes());
    }

    /**
     * 用户登录功能
     *
     * @param userLoginRequest 登录的信息
     * @param request
     * @return
     */
    @Override
    public User userLogin(UserLoginRequest userLoginRequest, HttpServletRequest request) {
        String userAccount = userLoginRequest.getUserAccount();
        String userPassword = userLoginRequest.getUserPassword();
        ThrowUtils.throwIf(StrUtil.hasBlank(userAccount, userPassword), ErrorCode.PARAMS_ERROR, "请输入账号或密码");
        ThrowUtils.throwIf(userAccount.length() < 4, ErrorCode.PARAMS_ERROR, "账号过短 账号长度不能小于4");
        ThrowUtils.throwIf(userPassword.length() < 8, ErrorCode.PARAMS_ERROR, "密码长度过短 密码长度不能小于8");
        //然后根据account和password作为条件 在mysql中查询 查询不到就不登录 查询得到就给登录
        //因为数据库中的密码是通过md5加密过的 所以这里先将密码用md5加密一下
        String encryptPassword = getEncryptPassword(userPassword);
//        注意:这里用的是md5加密后的密码
        User user = query().eq("userAccount", userAccount).eq("userPassword", encryptPassword).one();
        //如果查不到 那么就账号密码错误
        ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "账号或密码错误");
        //如果可以查到 那么就返回该用户的信息 用户名 用户简介 用户头像 用户角色 返回
        log.info("用户登录成功 返回用户信息并保存到Session中:{}", user);
        //保存登录信息(底层是键值对) 这里是保存到request的Session中 然后以后每次
        request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
        return user;
    }


    /**
     * 获取当前登录对象
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
        User loginUser = (User) userObj;
        //如果不能获取到就表示没有登录
        if (loginUser == null || loginUser.getId() == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        //从数据库中再查一遍 因为上面是从Session中获取的 如果说中途用户修改了名字 或者说简介(是在数据库中更新的) 那么session中是没有更新的 所以这里再到数据库中获取一遍
        User user = query().eq("id", loginUser.getId()).one();
        //再校验一次:如果说管理员将该用户给删除了就是null了
        ThrowUtils.throwIf(user == null, ErrorCode.NOT_LOGIN_ERROR);
        //最后返回user对象 这个返回是返回给业务中的 内部用的 不是给前端的 不用脱敏
        return user;
    }

    /**
     * 用户注销 退出登录
     *
     * @param request
     * @return
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        //1. 判断是否在登录状态
        Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
        if (userObj == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        //2. 如果在登录状态 那么就移除Session中的登录用户对象
        request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
        return true;
    }


    /**
     * 将user转换成userVO 这里体现到了代码复用:我在admin的权限中就可以直接获取user 一整个user 那普通用户查询的时候 我就可以调用admin里面的查询user然后再在这里替换成userVO即可
     *
     * @param user
     * @return
     */
    @Override
    public UserVO getUserVO(User user) {
        if (user == null) {
            return null;
        }
        UserVO userVO = new UserVO();
        BeanUtil.copyProperties(user, userVO);
        return userVO;
    }

    /**
     * 将userLIst转换成userVOLIst(脱敏) 这里体现到了代码复用:我在admin的权限中就可以直接获取userLIst 一整个userLIst 那普通用户查询的时候 我就可以调用admin里面的查询userLIst然后再在这里替换成userVOLIst即可
     *
     * @param userList
     * @return
     */
    @Override
    public List<UserVO> getUserVOList(List<User> userList) {
        //如果user集合中没有元素那么我也返回一个空集合
        if (CollUtil.isEmpty(userList)) {
            return List.of();
        }
        //否则如果里面有元素 那么就将里面的user一个一个转换成UserVO
        return userList.stream().map(this::getUserVO).toList();

    }


    /**
     * 根据id获取user
     *
     * @param id
     * @return
     */
    @Override
    public UserVO getUserById(Long id) {
        User user = adminService.getUserById(id);
        return getUserVO(user);
    }



    public void isLoginAdmin(HttpServletRequest httpServletRequest) {
        //获取当前登录的用户
        User user = getLoginUser(httpServletRequest);
        //判断该用户是不是管理员 不是管理员直接拦截
        ThrowUtils.throwIf(!isAdmin(user), ErrorCode.NO_AUTH_ERROR);
    }

    public Boolean isAdmin(User user) {
        return user != null && Objects.equals(user.getUserRole(), UserRoleEnum.ADMIN.getText());
    }



}
